package main

import (
	"autotradeclient/pb"
	"code.google.com/p/goprotobuf/proto"
	logger "github.com/alecthomas/log4go"
	"github.com/gorilla/websocket"

	"errors"
	"flag"
	"fmt"
	"strconv"
	"sync"
	"time"
)

var (
	trade_host string
)

func init() {
	flag.StringVar(&trade_host, "h", "ws://127.0.0.1/ws", "ws trade host")
}

type TradeRequester interface {
	GetQid() string
	GetUrl() string
	OnResponse(res *pb.UAResponse)
}

type TradeRequest struct {
	qid      string
	url      string
	notifyCh chan *pb.UAResponse
	exitCh   chan bool
}

func NewRealTradeRequest(qid, url string, exitCh chan bool) *TradeRequest {
	req := new(TradeRequest)
	req.qid = qid
	req.url = url
	req.notifyCh = make(chan *pb.UAResponse, 100)
	req.exitCh = exitCh
	return req
}

func (this *TradeRequest) GetQid() string {
	return this.qid
}

func (this *TradeRequest) GetUrl() string {
	return this.url
}

func (this *TradeRequest) OnResponse(res *pb.UAResponse) {
	select {
	case this.notifyCh <- res:
	case <-this.exitCh:
		return
	}
	return
}

type TradeClient struct {
	sync.Mutex
	Qid       int
	TradeFlag bool
	chExit    chan bool
	chReqUrl  chan string
	reqList   map[string]TradeRequester
}

func NewTradeClient() *TradeClient {
	client := new(TradeClient)
	client.TradeFlag = true
	client.chExit = make(chan bool)
	client.chReqUrl = make(chan string, 1024)
	client.reqList = make(map[string]TradeRequester)
	return client
}

func (this *TradeClient) start() error {
	ws, _, err := websocket.DefaultDialer.Dial(trade_host, nil)
	if err != nil {
		logger.Error("websocket connect err: %s", err)
		return err
	}
	//read
	go func() {
		defer func() {
			ws.Close()
			this.stop()
		}()
		ws.SetPingHandler(func(string) error {
			logger.Error("receive ping, and will send pong")
			ws.WriteControl(websocket.PongMessage, nil, time.Now().Add(10*time.Second))
			return nil
		})
		for {
			_, msg, err := ws.ReadMessage()
			if err != nil {
				logger.Error("wsread message error: %s", err)
				return
			}
			res := new(pb.UAResponse)
			if err = proto.Unmarshal(msg, res); err != nil {
				logger.Error("wsread proto.Unmarshal error: %s", err)
			} else {
				this.OnResTradeRequest(res)
			}
		}
	}()
	//write
	go func() {
		defer func() {
			ws.WriteControl(websocket.CloseMessage, nil, time.Now().Add(3))
			ws.Close()
		}()
		for {
			select {
			case url := <-this.chReqUrl:
				logger.Info("Send WebSoketRequest: %s", url)
				err := ws.WriteMessage(websocket.TextMessage, []byte(url))
				if err != nil {
					logger.Error("ws write message error: %s", err)
					return
				}
			case <-this.chExit:
				return
			}
		}
	}()
	return nil
}

func (this *TradeClient) stop() {
	if this.chExit != nil {
		close(this.chExit)
		this.chExit = nil
	}
}

func (this *TradeClient) MakeQid() string {
	this.Lock()
	defer this.Unlock()
	this.Qid++
	return strconv.Itoa(this.Qid)
}

func (this *TradeClient) AsyncTradeRequest(req TradeRequester) {
	this.Lock()
	this.reqList[req.GetQid()] = req
	defer this.Unlock()
	this.chReqUrl <- req.GetUrl()
}

func (this *TradeClient) CancelTradeRequest(req TradeRequester) {
	this.Lock()
	delete(this.reqList, req.GetQid())
	defer this.Unlock()
	url := "/cancel?qid=" + req.GetQid()
	this.chReqUrl <- url
}

func (this *TradeClient) OnResTradeRequest(res *pb.UAResponse) {
	this.Lock()
	defer this.Unlock()
	if req, ok := this.reqList[res.GetQid()]; ok {
		req.OnResponse(res)
	}
}

//direct true:buy  false:sell
func (this *TradeClient) RealPriceTrade(obj string, tradeVol int64, direct bool) (
	[]*pb.DealtRecord, error) {
	url := `/sell?obj=%s&entrust=1&volume=%d&output=pb&sub=1&qid=%s`
	if direct {
		url = `/buy?obj=%s&entrust=1&volume=%d&output=pb&qid=%s&sub=1`
	}
	qid := this.MakeQid()
	url = fmt.Sprintf(url, obj, tradeVol*100, qid)
	req := NewRealTradeRequest(qid, url, this.chExit)
	this.AsyncTradeRequest(req)
	defer this.CancelTradeRequest(req)
	var rlt []*pb.DealtRecord
	for {
		select {
		case res := <-req.notifyCh:
			if res.GetErr() > 0 {
				return nil, errors.New("receive errno:" + strconv.Itoa(int(res.GetErr())))
			}
			result := new(pb.TradeResult)
			if err := proto.Unmarshal(res.GetData(), result); err != nil {
				return nil, err
			}
			logger.Debug("receive obj:%s RealPriceTrade result:%v", obj, result)
			if result.GetRecords() != nil {
				rlt = append(rlt, result.GetRecords()...)
			}
			if result.GetDealtState() == 1 {
				return rlt, nil
			}
		case <-time.After(time.Second * 10):
			return nil, errors.New("RealPriceTrade 10s timeout!")
		case <-this.chExit:
			return nil, errors.New("exit")
		}
	}
	return nil, nil
}

//direct true:combine false:split
func (this *TradeClient) FndCombineSplit(fnd string, tradeVol int64, direct bool) (
	*pb.FundTradeResult, error) {
	url := `/fund/split?obj=%s&volume=%d&output=pb&sub=1&qid=%s`
	if direct {
		url = `/fund/combine?obj=%s&volume=%d&output=pb&sub=1&qid=%s`
	}
	qid := this.MakeQid()
	url = fmt.Sprintf(url, fnd, tradeVol*100, qid)
	req := NewRealTradeRequest(qid, url, this.chExit)
	this.AsyncTradeRequest(req)
	defer this.CancelTradeRequest(req)
	for {
		select {
		case res := <-req.notifyCh:
			if res.GetErr() > 0 {
				return nil, errors.New("receive errno:" + strconv.Itoa(int(res.GetErr())))
			}
			result := new(pb.FundTradeResult)
			if err := proto.Unmarshal(res.GetData(), result); err != nil {
				return nil, err
			}
			logger.Debug("receive obj:%s FndCombineSplit result:%v", fnd, result)
			if result.GetDealtState() == 0 {
				continue
			} else if result.GetDealtState() == -1 {
				return result, errors.New("CombineSplit fail:" + result.GetDealtMsg())
			} else {
				return result, nil
			}
		case <-time.After(time.Second * 10):
			return nil, errors.New("FndCombineSplit 10s timeout!")
		case <-this.chExit:
			return nil, errors.New("exit")
		}
	}
	return nil, nil
}

func (this *TradeClient) IsCanTrade() bool {
	return this.TradeFlag
}

func (this *TradeClient) TradeProccess(A, B, M string, tradeVol int64, overFlow bool) {
	this.TradeFlag = false
	go func() {
		if overFlow {
			AResult := make(chan bool)
			go func() {
				ATradeData, err := this.RealPriceTrade(A, tradeVol/2, true)
				if err != nil {
					logger.Error("RealPriceTrade fnd:%s BuyErr:%s", A, err)
				} else {
					logger.Info("RealPriceTrade fnd:%s BuyResult:%v", A, ATradeData)
				}
				AResult <- err == nil
			}()
			BResult := make(chan bool)
			go func() {
				BTradeData, err := this.RealPriceTrade(B, tradeVol/2, true)
				if err != nil {
					logger.Error("RealPriceTrade fnd:%s BuyErr:%s", B, err)
				} else {
					logger.Info("RealPriceTrade fnd:%s BuyResult:%v", B, BTradeData)
				}
				BResult <- err == nil
			}()
			if <-AResult && <-BResult {
				result, err := this.FndCombineSplit(M, tradeVol, true)
				if err != nil {
					logger.Error("FndCombineSplit fnd:%s CombineErr:%s", M, err)
					return
				}
				logger.Info("FndCombineSplit fnd:%s CombineResult:%s", M, result)
				MTradeData, err := this.RealPriceTrade(M, tradeVol, false)
				if err != nil {
					logger.Error("RealPriceTrade fnd:%s SellErr:%s", M, err)
					return
				}
				logger.Info("RealPriceTrade fnd:%s SellResult:%v", M, MTradeData)

				logger.Debug("Trade fnd%s complete finish timer 3s begin...", M)
				defer logger.Debug("Trade fnd%s complete finish timer 3s end...", M)
				this.TradeFlag = true
			}
		} else {
			MTradeData, err := this.RealPriceTrade(M, tradeVol, true)
			if err != nil {
				logger.Error("RealPriceTrade fnd:%s BuyErr:%s", M, err)
			} else {
				logger.Info("RealPriceTrade fnd:%s BuyResult:%v", M, MTradeData)
				result, err := this.FndCombineSplit(M, tradeVol, false)
				if err != nil {
					logger.Error("FndCombineSplit fnd:%s SplitErr:%s", M, err)
					return
				}
				logger.Info("FndCombineSplit fnd:%s SplitResult:%s", M, result)
				AResult := make(chan bool)
				go func() {
					ATradeData, err := this.RealPriceTrade(A, tradeVol/2, false)
					if err != nil {
						logger.Error("RealPriceTrade fnd:%s SellErr:%s", A, err)
					} else {
						logger.Info("RealPriceTrade fnd:%s SellResult:%v", A, ATradeData)
					}
					AResult <- err == nil
				}()
				BResult := make(chan bool)
				go func() {
					BTradeData, err := this.RealPriceTrade(B, tradeVol/2, false)
					if err != nil {
						logger.Error("RealPriceTrade fnd:%s SellErr:%s", B, err)
					} else {
						logger.Info("RealPriceTrade fnd:%s SellResult:%v", B, BTradeData)
					}
					BResult <- err == nil
				}()
				logger.Debug("Trade fnd%s complete finish timer 3s begin...", M)
				defer logger.Debug("Trade fnd%s complete finish timer 3s end...", M)
				time.Sleep(3 * time.Second)
				this.TradeFlag = <-AResult && <-BResult
			}
		}
	}()
}
